package com.binance.notification.data.sharding;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;

import org.springframework.stereotype.Component;

import com.binance.master.data.sharding.TableRuleSharding;
import com.binance.master.utils.HashAlgorithms;

import io.shardingsphere.api.algorithm.sharding.ListShardingValue;
import io.shardingsphere.api.algorithm.sharding.PreciseShardingValue;
import io.shardingsphere.api.algorithm.sharding.ShardingValue;
import io.shardingsphere.api.algorithm.sharding.complex.ComplexKeysShardingAlgorithm;
import io.shardingsphere.api.algorithm.sharding.standard.PreciseShardingAlgorithm;
import io.shardingsphere.api.config.strategy.StandardShardingStrategyConfiguration;

@Component
public class AlertPriceTableRule extends TableRuleSharding {

    public AlertPriceTableRule() {
        super();
        this.setLogicTable("alert_price");
        this.setActualDataNodes("db1.alert_price_${0..19}");
        StandardShardingStrategyConfiguration configuration =
                new StandardShardingStrategyConfiguration("user_id", new AlertPriceTableShardingAlgorithm());

        this.setTableShardingStrategyConfig(configuration);
        this.setKeyGeneratorColumnName("id");
    }

    public static class AlertPriceTableShardingAlgorithm implements PreciseShardingAlgorithm<String> {

        @Override
        public String doSharding(Collection<String> availableTargetNames, PreciseShardingValue<String> shardingValue) {
            final List<String> tables = new ArrayList<String>(availableTargetNames);
            int index = Math.abs(HashAlgorithms.FNVHash1(shardingValue.getValue()) % availableTargetNames.size());
            return tables.get(index);
        }
    }

    @Deprecated
    public static class AlertPriceTableComplexKeysShardingAlgorithm implements ComplexKeysShardingAlgorithm {

        @Override
        public Collection<String> doSharding(Collection<String> availableTargetNames,
                Collection<ShardingValue> shardingValues) {
            final List<String> tables = new ArrayList<String>(availableTargetNames);
            Collection<String> pairValues = getShardingValue(shardingValues, "pair");
            Collection<String> userIdValues = getShardingValue(shardingValues, "user_id");
            Collection<Long> idValues = getShardingValue(shardingValues, "id");
            List<String> shardingSuffix = new ArrayList<>();

            for (String userIdVal : userIdValues) {
                for (String pairVal : pairValues) {
                    for (Long id : idValues) {
                        int index = Math.abs(HashAlgorithms.FNVHash1(pairVal + "_" + userIdVal + "_" + id.toString())
                                % availableTargetNames.size());
                        shardingSuffix.add(tables.get(index));
                    }
                }
            }

            return shardingSuffix;
        }

        private <T extends Comparable<?>> Collection<T> getShardingValue(Collection<ShardingValue> shardingValues,
                final String key) {
            Collection<T> valueSet = new ArrayList<>();
            Iterator<ShardingValue> iterator = shardingValues.iterator();
            while (iterator.hasNext()) {
                ShardingValue next = iterator.next();
                if (next instanceof ListShardingValue) {
                    @SuppressWarnings("unchecked")
                    ListShardingValue<T> value = (ListShardingValue<T>) next;
                    if (value.getColumnName().equals(key)) {
                        return value.getValues();
                    }
                }
            }
            return valueSet;
        }

    }
}
